// Ofri Keidar 302933981 group 05

package controller;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;

import model.Model;
import model.apartment.*;
import model.database.DBException;

/**
 * This class implements Controller interface. The class performs transactions on system's data, using the database 
 */
public class Transactor implements Controller {

	// names of Building element's tags
	private static final String BUILDING_TAG = "Building";	
	private static final String STREET_TAG = "street";
	private static final String BUILDING_NUMBER_TAG = "buildingNumber";
	private static final String APARTMENTS_TAG = "Apartments";
	
	private Model apartmentsManager; // used to manage creating and retrieving apartments
		
	/**
	 * Constructor- creates a new object to manage the transactions
	 * @param apartmentsManager- model object to be used
	 * @throws DBException- throws database exception if could not initialize system
	 */
	public Transactor(Model apartmentsManager) throws DBException {		
		
		// set apartments manager
		this.apartmentsManager = apartmentsManager;
	}

	/**
	 * Gets all vacant flats from the apartments manager.
	 * Flats are sorted by total area in ascending order
	 * Throws database exception if database encountered a problem 
	 */
	@Override
	public List<Flat> getVacantFlats() throws DBException {
		
		// return all vacant flats in system
		return apartmentsManager.getVacantFlats();
	}

	/**
	 * Gets vacant flats with given room number from the apartments manager.
	 * Flats are sorted by total area in ascending order
	 * Throws database exception if database encountered a problem 
	 */
	@Override
	public List<Flat> getVacantFlats(double roomNum) throws DBException {

		// return all vacant flats in system with given room number
		return apartmentsManager.getVacantFlats(roomNum);		
	}
	
	/**
	 * Gets vacant flats with given room number at given address from the apartments manager.
	 * Flats are sorted by total area in ascending order
	 * Throws database exception if database encountered a problem 
	 */
	@Override
	public List<Flat> getVacantFlats(double roomNum, String stName, int bldNumber)
			throws DBException {
		
		// return all vacant flats at given address
		return apartmentsManager.getVacantFlats(roomNum, stName, bldNumber);				
	}

	/**
	 * Gets flats at given address from the apartments manager.
	 * Flats are sorted by total area in ascending order
	 * Throws database exception if database encountered a problem  
	 */
	@Override
	public List<Flat> getFlats(String stName, int bldNumber) throws DBException {

		// return all flats at given address
		return apartmentsManager.getFlats(stName, bldNumber);
	}

	/**
	 * Gets all flats from the apartments manager.
	 * Flats are sorted by total area in ascending order
	 * Throws database exception if database encountered a problem  
	 */
	@Override
	public List<Flat> getFlats() throws DBException {
		
		// return all flats in system
		return apartmentsManager.getAllFlats();
	}
	
	/**
	 * Gets all penthouses from the apartments manager.
	 * Penthouses are sorted by total area in ascending order
	 * Throws database exception if database encountered a problem  
	 */
	@Override
	public List<Penthouse> getPenthouses() throws DBException {
				
		// return all penthouses in system
		return apartmentsManager.getAllPenthouses();
	}

	/**
	 * Gets all garden apartments from the apartments manager.
	 * Garden apartments are sorted by total area in ascending order
	 * Throws database exception if database encountered a problem  
	 */
	@Override
	public List<GardenApartment> getGardenApartments() throws DBException {
		
		// return all garden apartment in system
		return apartmentsManager.getAllGardenAprtmnts();
	}

	/**
	 * Uses the apartments manager to get list of vacant apartments, and returns number of elements in this list.
	 * Throws database exception if database encountered a problem 
	 */
	@Override
	public int getVacantAprtsNum() throws DBException {
		
		// get all vacant apartments in system and return their amount
		return apartmentsManager.getVacantApartments().size();
	}
	
	/**
	 * Uses the apartments manager to get list of vacant apartments at given address, and returns number of elements in this list.
	 * Throws database exception if database encountered a problem 
	 */
	@Override
	public int getVacantAprtsNum(String stName, int bldNumber)
			throws DBException {
		
		// get all vacant apartments at given address and return their amount
		return apartmentsManager.getVacantApartments(stName, bldNumber).size();
	}
	
	/**
	 * Parses XML file in order to extract all buildings.
	 * If given file name is wrong or file's data does not match the apartments'
	 * parameters format, returns False.
	 * If could parse file and create the buildings and apartments, returns True.
	 * Throws database exception if database encountered a problem 
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public boolean parseBuildingsFromXML(Document buildingsDocument) throws DBException {
	
		// get document's root element
		Element root = buildingsDocument.getRootElement();			
		
		// get list of all building elements
		List buildings = root.getChildren(BUILDING_TAG);
		
		// scan all building elements and parse all buildings
		int numOfBuildings = buildings.size();
		
		// check if there are any buildings in the document
		if (numOfBuildings == 0) {
			
			// no buildings in document
			return false;
		}
		
		for (int i = 0 ; i < numOfBuildings ; ++i) {
			
			// get current building element
			Element currBuilding = (Element) buildings.get(i);
			
			// check if street name tag exists
			if (currBuilding.getChild(STREET_TAG) == null) {
				
				// no street name- cannot parse building
				return false;
			}

			// get current building's address
			String stName = currBuilding.getChild(STREET_TAG).getValue();
			
			// check if street name is given
			if (stName.equals("")) {
				
				// no street name- cannot parse building
				return false;
			}
			
			// check if building number tag exists
			if (currBuilding.getChild(BUILDING_NUMBER_TAG) == null) {
				
				// no building number- cannot parse building
				return false;
			}
			
			// get current building's number
			int bldNumber;
			try {
				bldNumber = Integer.parseInt(currBuilding.getChild(BUILDING_NUMBER_TAG).getValue());
			} catch (NumberFormatException e) {
				// building's number tag's content is not a numeric value
				return false;
			}
							
			// extract current building's apartments
			List< Map<String, String> > apartmentsParameters = new LinkedList<Map<String,String>>();
			List<String> apartmentsTypes = new LinkedList<String>();
			varifyBuildingParameters(currBuilding, apartmentsTypes, apartmentsParameters);
			
			// check file's content correctness
			if (apartmentsParameters.isEmpty()) {
				
				// current building's data could not be parsed
				return false;
			}				
							
			// create building according to parsed data
			apartmentsManager.addNewBuilding(apartmentsTypes, apartmentsParameters, stName, bldNumber);
		}
		
		// data has been successfully parsed
		return true;
	}
	
	/**
	 * Checks that the parameters for the apartments from given building element are correct.
	 * If parameters are correct, adds them to the parameter map.
	 * If not, the parameter map is empty
	 * @param building- building element to be parsed (input parameter)
	 * @param apartmentsTypes- output parameter to hold types of parsed apartments 
	 * @param apartmentsParameters- output parameter to hold the parsed parameters
	 */
	@SuppressWarnings("rawtypes")
	private void varifyBuildingParameters(Element building, List<String> apartmentsTypes, List< Map<String, String> > apartmentsParameters) {
		
		// get the node that holds the apartments
		Element apartmentsNode = building.getChild(APARTMENTS_TAG);
		
		// check that the node exists
		if (apartmentsNode == null) {
			
			// no apartments in building
			apartmentsParameters.clear();
			return;
		}
		
		// get the apartments 
		List apartments = apartmentsNode.getChildren();
		
		// scan the apartment elements and parse their parameters according to their type
		int numOfApartments = apartments.size(); 
		for (int i = 0 ; i < numOfApartments ; ++i) {			

			// parameters map of current apartment
			Map<String, String> currApartmentParamterMap = new HashMap<String, String>();
			
			// add current apartment's type
			apartmentsTypes.add(((Element) apartments.get(i)).getName());
			
			// current apartment's parameters
			List currApartmentParameters = ((Element) apartments.get(i)).getChildren();
			
			// get current apartment's parameters
			int numOfParameters = currApartmentParameters.size();
			for (int j = 0 ; j < numOfParameters ; ++j) {
				
				// store current parameter with its tag name
				currApartmentParamterMap.put( ((Element)currApartmentParameters.get(j)).getName(), ((Element)currApartmentParameters.get(j)).getValue());
			}
			
			// check parameters format's correctness
			if (!apartmentsManager.checkApartmentsParameters(apartmentsTypes.get(i), currApartmentParamterMap)) {
				
				// error in parameter's format
				apartmentsParameters.clear();
				return;
			}
			
			// store current apartment's parameters with building's other apartments' parameters
			apartmentsParameters.add(currApartmentParamterMap);
		}
		
		// done extracting apartments details
		return;
	}
	
	/**
	 * Gets parameters of all apartments and new building's address. 
	 * If there already another building at given address, no building is not created and False is returned. 
	 * Otherwise, creates a new building with given details and True is returned.
	 * Throws database exception if database encountered a problem 
	 */
	@Override
	public boolean addBuilding(LinkedList<String> apartmentTypes,
			LinkedList<Map<String, String>> parameterList, String stName,
			int bldNumber) throws DBException {
		
		// check if there is not already another building at given address
		if (apartmentsManager.getBuilding(stName, bldNumber) != null) {
			
			// address is occupied, cannot add a new building there
			return false;
		}
		
		// add new building at given address
		apartmentsManager.addNewBuilding(apartmentTypes, parameterList, stName, bldNumber);
		return true;
	}
	
	/**
	 * Uses apartments manager to update resident name at apartment with given number at given address.
	 * Returns True if could update, False if not.
	 * Throws database exception if database encountered a problem 
	 */
	@Override
	public boolean updateResident(String stName, int bldNum, int apartmentNum,
			String newResident) throws DBException {

		// update resident name of specified apartment
		return apartmentsManager.updateResident(stName, bldNum, apartmentNum, newResident);
	}
}